#ifndef ROBOTJOINTMOTIONTRACK_H
#define ROBOTJOINTMOTIONTRACK_H

#include <QVariantAnimation>

#include "robot.h"
#include "robotcommands.h"

class RobotAction;

/**
 * @brief Animates the motion of a single robot joint.
 *
 * The Qt animation framework is used to animate the robot. A QVariantAnimation
 * "animates" a value with nice interpolation abilities, so RobotJointMotionTrack
 * subclasses it with the animated value being the joint's current angle. The
 * updateCurrentValue function is overriden to send a command to the robot
 * to change the position of the joint to the new value.  Functionality has also
 * been added to emit a key frame signal whenever a key frame is reached.
 *
 * This class contains the real functionality for modifying an action, because
 * the RobotMotionTrack class simply calls the functions of this class, either
 * one at a time or for every joint.  Please note that key frames are always
 * defined at the start and end of the animation (i.e., step 0.0 and 1.0 are
 * always defined by this class).
*/
class RobotJointMotionTrack : public QVariantAnimation
{
    Q_OBJECT

private:
    Robot* _robot;
    int _jointID;
    int _prevTime;

    void refreshDuration();

protected:
    /**
     * @brief Reimplemented to update the angle of the joint with the new value.
     *
     * @param value
     */
    void updateCurrentValue(const QVariant &value);
    /**
     * @brief Reimplemented to update the time of the animation and emit key frame signals.
     *
     * @param msec
     */
    void updateCurrentTime(int msec);

public:
    /**
     * @brief Constructor.  Creates a new RobotJointMotionTrack object.
     *
     * @param robot The Robot object that this joint motion track will animate.
     * @param jointID The joint ID that this joint motion track will animate.
     * @param parent
     */
    explicit RobotJointMotionTrack(Robot* robot, int jointID, QObject *parent = 0);
    ~RobotJointMotionTrack();

    void setRobot(Robot* robot);

    /**
     * @brief Overrides the default set duration behavior.
     *
     * The default set duration behavior simply stretches the animation, because key
     * frames are at "steps" (increments relative to the duration of the animation).
     * This behavior is overridden so that increasing/decreasing the duration
     * appends time/clips the animation.
     *
     * It is worth noting, however, that because a joint motion track has steps 0.0 (the start)
     * and 1.0 (the end) always defined, the behavior is to keep the value that was at
     * 1.0 in place; it does not remain in place when the duration is increased, like other
     * key frames, and it does not get clipped when the duration is decreased.
     *
     * @param msecs The new duration for this animation, in milliseconds.
     */
    void setDuration(int msecs);

    //Functions for information about the track
    /**
     * @brief Returns the joint ID that this joint motion track animates.
     *
     */
    int jointID() const;

    //Functions for manipulating key frames
    /**
     * @brief Inserts a key frame at the given time, linearly interpolating the value (angle)
     * to insert. If a key frame already exists, no change is made.
     *
     * Returns the value that was inserted or INVALID_KEYFRAME if the key frame already
     * exists.  Inserting past the duration of this track increases its duration and
     * sets the key frame as the end key frame with a value of 0.
     *
     * @param msec The timestamp of the new key frame, in milliseconds.
     */
    float insertKeyFrameAt(int msec);

    /**
     * @brief Sets the value of the key frame at a given time.
     *
     * Returns the value set (which may have been corrected for min/max angles) or
     * INVALID_KEYFRAME if there was an error.
     *
     * @param msec The timestamp of the key frame, in milliseconds.
     * @param theta The new angle for the key frame.
     */
    float setKeyFrameAt(int msec, float theta);

    /**
     * @brief Deletes the key frame.
     *
     * Returns the value of the key frame that was deleted or INVALID_KEYFRAME if
     * the key frame does not exist.  Deleting the end key frame (msec == duration())
     * will cause the duration of the animation to decrease to the previous
     * key frame.  You cannot delete the key frame at 0.
     *
     * @param msec The timestamp of the key frame to delete, in milliseconds.
     */
    float deleteKeyFrameAt(int msec);

    /**
     * @brief Moves a key frame from one time to another.
     *
     * Returns true if the move was successful.  Moving a key frame past the
     * end of the animation (newMsec > duration), will increase the duration
     * of the animation, setting the key frame as the new end key frame.
     *
     * @param msec The current timestamp of the key frame to move.
     * @param newMsec The new timestamp for the key frame.
     */
    bool moveKeyFrame(int msec, int newMsec);

    //Functions for information about the key frames
    /**
     * @brief Returns the value (angle) of the key frame at a given time or
     * INVALID_KEYFRAME if the key frame doesn't exist.
     *
     * @param msec The timestamp of the key frame.
     */
    float keyValueAt(int msec) const;

    /**
     * @brief Returns the key frames of this animation as a list
     * of timestamp/angle pairs.
     *
     */
    QList< QPair <int, float> > keyFrames() const;

    /**
     * @brief Returns the next key frame after the given time
     *
     * @param curTime The timestamp where the search is started
     */
    int nextKeyFrame(int curTime) const;

    /**
     * @brief Returns the previous key frame before the given time
     *
     * @param curTime The timestamp where the search is started
     */
    int prevKeyFrame(int curTime) const;

    //Helper functions
    /**
     * @brief Helper function that converts "steps" (values between 0.0 and 1.0 that
     * represent a percentage along the duration of this animation) to milliseconds.
     *
     * @param step The step value to convert.
     */
    inline int convertToMsec(qreal step) const;

    /**
     * @brief Helper function that converts milliseconds to steps.
     *
     * @param msec The milliseconds to convert.
     */
    inline qreal convertFromMsec(int msec) const;


    //Friend functions to help with serialization of a robot action
    friend QTextStream &operator<<(QTextStream &out, const RobotAction &action);
    friend QDataStream &operator<<(QDataStream &out, const RobotAction &action);


signals:
    /**
     * @brief Signal emitted whenever a key frame is reached during animation.
     *
     * @param msec The timestamp (in milliseconds) of the key frame.
     */
    void keyFrame(int msec);

    /**
     * @brief Signal emitted whenever the duration of this joint motion track
     * changes.
     *
     * @param msec The new duration, in milliseconds.
     */
    void durationChanged(int msec);
};

#endif // ROBOTJOINTMOTIONTRACK_H
